Using the Editor "An ass loaded with gold climbs to the top of the castle" English Proverb Introduction One of the most requested tools over the past few years has been a plug-in editor which you can easily incorporate into an application. Well here it is. Rather than implement a basic little line editor, we decide to endow Gold with a beefy "Arnold" editor that includes the following features: Automatic dynamic word wrap Clipboard support allowing for cut/copy and paste Full mouse support Support for optional headers and footers Hooks to allow customization Figure 15.1 Edit Windows Basic Principles Many of the principles used with the editing functions are identical with those used for listing and browsing data (discussed in the last chapter). Gold needs to know the main properties of the data and the characteristics of the window in which the data will be displayed. For example, before an edit window can be displayed, Gold needs to know some of the following information: What type of data is to be displayed, e.g. a single linked list, double linked list, array, etc. Where the data is stored, i.e. the name of the variable. The dimensions and style of the window. The window title. Any optional memo headers and footers. What colors are to be used. You define all these memo characteristics by creating a variable (of type MemoCfg, for memo configuration), initializing the variable, and then setting various components of the variable to define your specific needs. In other words, to display a memo in a window you need at least two variables: a list configuration variable, and a variable used to store the data. Having configured the MemoCfg variable and loaded the data the following function can be used to provide an edit window: RunMemo(var MemoDetails: MemoCfg; Tit:StrScreen); Displays the data identified in MemoDetails in an edit window. Defining the Memo Configuration The key to displaying a memo window is to declare a variable of type MemoCfg, and then call Gold functions to set MemoCfg to meet your specific needs. Initializing MemoCfg Always, always, always call InitMemoCfg to initialize the MemoCfg variable before calling the other memo procedures and functions. InitMemoCfg(var MemoDetails: MemoCfg); Initializes a memo configuration variable. Assigning the Data Source Having initialized the MemoCfg variable you then need to update the variable with information about the memo data source. Gold can display the contents of a SingleLL, DoubleLL, or a string array. Dependent upon your data source, you would call one of the following procedures to update the MemoCfg variable with the data source details: MemoAssignSLL(var MemoDetails: MemoCfg; var MemoSource: SingleLL); Sets the MemoCfg variable data source as a single linked list stored in the variable MemoSource. MemoAssignDLL(var MemoDetails: MemoCfg; var MemoSource: DoubleLL); Sets the MemoCfg variable data source as a double linked list stored in the variable MemoSource. MemoAssignArray(var MemoDetails: MemoCfg; var MemoSource; StrLen:Byte;ArrayElements:byte); Sets the MemoCfg variable data source as a string array stored in the variable MemoSource. The last two parameters define the length of each string in the array, and the number of elements in the array, respectively. Setting Window Properties When the MemoCfg variable is initialized, the window properties (such as the window position and dimensions) are set to some standard defaults. If these defaults are not appropriate, you can customize them with the following two procedures MemoSetWin(var MemoDetails: MemoCfg; X1,Y1,X2,Y2:integer; Style:byte); Defines the upper left and lower right corners of the window along with the window style. (Refer to chapter 4 for a discussion of window styles.) MemoSetGaps(var MemoDetails: MemoCfg; LeftGap,RightGap,BotGap,TopGap: byte); This procedure allows you to set the gap or whitespace area around the memo body. The editable data does not need to fill the entire window. If you want to change the window defaults, update the following variables: MemoVars.X1, MemoVars.Y1, MemoVars.X2, MemoVars.Y2, and MemoVars.WiStyle. Setting the Window Colors As usual, the default colors are defined in the TINT structure, and the following elements pertain to memo windows: MemoHi, MemoNorm, MemoBlock, MemoScrollbarHi, MemoScrollbarNorm, MemoBorder1, MemoBorder2, MemoBorderOff, MemoTitle, MemoHeaders, MemoIcons, You can modify the defaults (used by any new memo windows initialized after the modification) by using the standard GoldSetColor procedure. You can modify the colors of a specific memo window by using the following function: MemoSetColor(var MemoDetails: MemoCfg; A:TintElement;C:byte); Sets the color of one of the elements of a memo window. The following code is an extract from DEMMEM6.PAS which customizes the list display colors: procedure CustomizeColors; {} begin MemoSetColor(MemoSettings,MemoHi, LightGrayonMagenta); MemoSetColor(MemoSettings,MemoBlock,YellowOnBlue); MemoSetColor(MemoSettings,MemoNorm,YellowOnMagenta); MemoSetColor(MemoSettings,MemoScrollbarHi, WhiteOnMagenta); MemoSetColor(MemoSettings,MemoScrollbarNorm, WhiteOnMagenta); MemoSetColor(MemoSettings,MemoBorder1, YellowOnMagenta); MemoSetColor(MemoSettings,MemoBorder2, YellowOnMagenta); MemoSetColor(MemoSettings,MemoBorderOff, BlackOnMagenta); MemoSetColor(MemoSettings,MemoTitle,WhiteOnMagenta); MemoSetColor(MemoSettings,MemoHeaders, GreenOnMagenta); MemoSetColor(MemoSettings,MemoIcons, LightCyanOnMagenta); end; { CustomizeColors } Using Headers and Footers Memo windows support multi-line headers and footers. Out of the box, Gold limits the number of heading lines and footer lines to 4, but you can change the number by assigning a different value to the constants ListMaxHeaders and ListMaxFooters declared in GOLDLIST. To minimize memory usage, all headers and footers must be stored as string variables in your program. (Gold simply stores a pointer to the variable.) The following six procedures can be used to manage the headers and footers: MemoAssignHeader(var MemoDetails: MemoCfg; Line:byte; var Heading:string); Sets the memo configuration variable to display a string as a header. A string variable must be passed -- a string literal will not be accepted. The line must be a value in the range 1 to ListMaxHeaders (which defaults to 4). MemoAssignFooter(var MemoDetails: MemoCfg; Line:byte; var Footnote:string); Sets the memo configuration variable to display a string as a footer. A string variable must be passed -- a string literal will not be accepted. The line must be a value in the range 1 to ListMaxFooters (which defaults to 4). MemoRemoveHeader(var MemoDetails: MemoCfg; Line:byte); Removes a header line which was previously assigned with MemoAssignHeader. MemoRemoveFooter(var MemoDetails: MemoCfg; Line:byte); Removes a header line which was previously assigned with MemoAssignFooter. MemoScrollHeader(var MemoDetails: MemoCfg; On:boolean); Pass TRUE if the headings should be scrolled right and left when the user scrolls the memo body right and left, or FALSE if the header should remain fixed. MemoScrollFooter(var MemoDetails: MemoCfg; On:boolean); Pass TRUE if the headings should be scrolled right and left when the user scrolls the memo body right and left, or FALSE if the header should remain fixed You can automatically center a header or footer by prefixing the string with the ^ character. Refer to the demo file DEMMEM1.PAS for an example of a memo with a heading. Controlling Word Wrap IMPORTANT NOTE: The GOLDFLAG.INC file includes a conditional compiler directive named WORDWRAP. This compiler directive must be enabled (i.e. there should be no spaces either side of the $ symbol in the {$DEFINE statement) if your program is to support word wrapping. Gold can be configured to edit with or without automatic word wrapping. When word wrapping is disabled, the editor functions like a normal text editor, e.g. just like DOS' EDIT program. By default, word wrapping is enabled, but the following procedure controls the word wrapping state: MemoSetWordWrap(var MemoDetails: MemoCfg; On:boolean); Pass TRUE to enable word wrapping and FALSE to disable it. Gold word wraps all text in the current paragraph. You indicate the end of a paragraph by inserting an end of paragraph character in the text stream. This character is defined in the GOLDMEMO unit by the variable MemoVars.EndofParaCode. The code will default to char(20) and is displayed as "". This same code will be inserted into the text during an edit session when the user presses the Enter key. Before the text is initially displayed at the beginning of a RunMemo session, Gold will automatically wrap the text. It is very important that the source text includes embedded end of paragraph codes where appropriate. Otherwise, Gold will assume the entire stream is one big happy paragraph! Listed below is an extract from DEMMEM1.PAS which shows the text stream being created. Notice the embedded end of paragraph codes inserted at strategic places in the text.: procedure BuildSLL; {} var I: integer; begin InitSLLStr(TextData); I := 0; SLLSetActiveList(TextData); inc(I,SLLAddStr('Turbo Pascal with Objects ...')); inc(I,SLLAddStr('programming system for DOS...')); inc(I,SLLAddStr('development.Features DOS...')); inc(I,SLLAddStr('application frameworks; wo...' )); inc(I,SLLAddStr('create DOS, Windows Dynamic ...')); inc(I,SLLAddStr('across platforms.Better ...')); inc(I,SLLAddStr('with the award-winning Tech....')); if I <> 0 then begin PromptOK(' ERROR ','Not enough memory !'); halt; end; end; {BuildSLL} If you want to display an empty window with no initial text, you must (as a minimum) assign one end of paragraph marker to the text stream -- this is similar to many word processors where a new document always contains an end of document symbol. The following code is an extract from DEMMEM2.PAS which attempts to load the source text from a file. If the file load fails, a single end of paragraph marker is assigned to the text: procedure BuildSLL; {} begin InitSLLStr(TextData); SLLSetActiveList(TextData); if SLLLoadFromFile(TextFileName) <> 0 then {bad} if SLLAddStr(MemoVars.EndofParaCode) <> 0 then begin PromptOK(' ERROR ','Not enough memory'); halt; end; end; {BuildSLL} Using the Clipboard That's right, Gold edit session support an internal clipboard enabling cut and paste operations! IMPORTANT NOTE: The GOLDFLAG.INC file includes a conditional compiler directive named CUTANDPASTE. This compiler directive must be enabled (i.e. there should be no spaces either side of the $ symbol in the {$DEFINE statement) if your program is to support cut and paste. Having said that there is support for cut and paste, there is not a lot left to say. Gold is initially configured to support standard key operations like Shift-Del and Ctrl-Ins. These cut and paste keystrokes are defined in the MemoVars global variable, and you may modify them to support custom keystrokes for copy and paste operations. List below is an extract from the initialization procedure which defines these keystrokes: procedure MemoDefaultSettings; {} begin with MemoVars do begin DeltoScrapKey := 263; {Shift-Del} DelBlockKey := 339; {Del} CopyToScrapkey := 260; {Ctrl-Ins} InsfromScrapKey := 261; {Shift-Ins} MarkBlockStartKey := 321; {F7} MarkBlockEndKey := 322; {F8} HideBlockKey := 2; {Ctrl-B} end; end; Customizing General Edit Keystrokes In the last section you learned how to customize the keystrokes related to the cut and paste operations. List below are some other keystrokes which are defined in the global variable MemoVars. You may assign new values to any of these keystrokes. procedure MemoDefaultSettings; {} begin with MemoVars do begin WordRightKey := 372; {Ctrl-Right} WordLeftKey := 371; {Ctrl-Left} TopOfWindowKey := 375; {Ctrl-Home} BotOfWindowKey := 373; {Ctrl-End} TopofMemoKey := 388; {Ctrl-PgUp} BotofMemoKey := 374; {Ctrl-PgDn} EndEditKey := 324; {F10} EscEditKey := 27; {Esc} end; end; Determining the Dirty State Each MemoCfg variable keeps track of whether the data has been edited. When the variable is initialized (using InitMemoCfg) the state is set to FALSE, i.e. the memo has not been edited. Gold will automatically set the dirty flag to true if the data is edited in any way. The following two procedures can be used to get and set the state of the dirty flag: MemoIsDirty(var MemoDetails: MemoCfg):boolean; Returns TRUE if the memo has been edited. MemoSetDirty(var MemoDetails: MemoCfg; On:boolean); Sets the dirty flag which indicates whether or not the data has been edited. The demo files DEMMEM2.PAS and DEMMEM5.PAS use these functions during file save operations. Using Memo Hooks Gold provides two different hooks for customizing memo windows: the character hook and the hind hook. The Character Hook A character hook is a procedure which is called every time a key is pressed or a mouse button is clicked while a memo window has focus. The hooked procedure is called before the key is processed by Gold. The hook is particularly useful for trapping special keys like F1 for help, or Ctrl-Y to delete a file, for example. All you have to do is create a procedure following some specific rules, and then call the MemoAssignCharHook procedure to instruct Gold to call your procedure every time a key is pressed. For a procedure to be eligible as a character hook it must adhere to the following rules: The procedure must be declared as a far procedure at the root level. Refer to the section Understanding Hooks in Chapter 3 for further information. The procedure must be declared with four parameters. The first parameter is of type MemoCfgPtr, followed by one variable parameter of type word and two variable parameters of type byte. These parameters identify the MemoCfg variable used to display the memo along with the user input, i.e. the Key, X and Y. The following procedure declaration follows these rules: {$F+} procedure MyCharHook(MP: MemoCfgPtr; var Code:word;var X,Y:byte); begin {some code} end; {MyCharHook} {$F-} The following procedure is then called to instruct Gold to call your procedure after each input: MemoAssignCharHook(var MemoDetails: MemoCfg; Proc:MemoCharHook); Instructs Gold to call the specified procedure before processing each user input to a memo window. If, subsequently, you want to remove the character hook, execute the following procedure: MemoRemoveCharHook(var MemoDetails: MemoCfg); Removes a list character which was previously assigned with MemoAssignCharHook. The demo file DEMMEM7.PAS illustrates a character hook providing Ctrl-Y line deletion support. The Hind Hook A memo hook is similar to a character hook, except the hook is called after an input has been processed by Gold. If you want to write some additional information to the screen based on the cursor, you should take advantage of the hind hook. For a procedure to be eligible as a hind hook it must adhere to the following rules: The procedure must be declared as a far procedure at the root level. Refer to the section Understanding Hooks in Chapter 3 for further information. The procedure must be declared with one parameter of type MemoCfgPtr. The following procedure declaration follows these rules: {$F+} procedure MyHineyHook(MemoDetailsPtr:MemoCfgPtr); begin {some code} end; {MyHineyHook} {$F-} The following procedure is then called to instruct Gold to call your procedure after each input: MemoAssignHindHook(var MemoDetails: MemoCfg; Proc:MemoHindHook)); Instructs Gold to call the specified procedure after processing each user input to a list window, and once when the window is first displayed. The variable parameter which is passed to the procedure is actually a pointer to the MemoCfg variable which is being used by the memo. You can de-reference the pointer to access the variable, for example: Total := MemoDetails^.TotalNodes If, subsequently, you want to remove the hind hook, execute the following procedure: MemoRemoveHindHook(var MemoDetails:MemoCfg); Removes a memo hind which was previously assigned with MemoAssignHindHook. The demo file DEMMEM8.PAS shows a hind hook used to displayed the cursor position, and indicates when the file has been modified. Launching Memos on the Desktop The desktop equivalent of RunMemo, is LaunchMemo which is described as follows: LaunchMemo(var MemoDetails: MemoCfg;Tit:StrScreen; CloseProc:MemoCloseProc): byte; Adds a memo window to the desktop. The function returns the number (or handle) of the newly created window. Like its Run counter part, the LaunchMemo function is passed a MemoCfg variable and a title. Additionally, LaunchMemo is passed a close function as a third parameter. This close function is called whenever the user tries to close the window on the desktop, and it provides you with an opportunity to do any housekeeping (such as disposing of linked lists) before the window is closed. For a function to be eligible as a memo close hook it must adhere to the following rules: The function must be declared as a far function at the root level. Refer to the section Understanding Hooks in Chapter 4 for further information. The function must be declared with one parameter of type MemoCfgPtr, and one integer. The function must return a boolean value. The following procedure declaration follows these rules: {$F+} function GoodbyeList(var MCP: MemoCfgPtr; Handle:integer): boolean; {} begin PopUpSetActive(ListMenu,201,true); SLLDestroy; GoodbyeList := true; end; { GoodbyeList } {$F-} If the function returns false, the window will not close. The first parameter passed to the function is a pointer to the MemoCfg variable used to display the list. You can de-reference this pointer to access the variable, for example: Total := MemoDetails^.ActiveNode The demo file DEMMEM5.PAS shows the LaunchMemo function in all its glory. Saving Files in Hooks or on the Desktop IMPORTANT NOTE: Behind the scenes, a copy of the line containing the cursor is maintained by Gold. As edits are made to the memo, Gold simply updates the single line copy -- this offers improved performance. When the user moves lines, the copy is posted back to the original data and a copy of the new line is made. "Why do I care" I hear you ask. Read On If you plan to save the memo in a hook, or as part of the close function, you must instruct Gold to update the original data with the current line using the following function: MemoStoreActiveLine(var MemoDetails: MemoCfg); Instructs Gold to write back (to the original data source) all the edits performed on the active line, i.e. the line containing the cursor. This procedure is used in DEMMEM5.PAS. Error Handling The RunMemo function has the potential to fail. For example, there may be insufficient memory to create the window. After calling RunMemo , always call the function, LastMemoError to see if the operation was successful. If LaunchMemo returns a zero, the window was not created.